package io.cici.cc.mybatis.lite.binding;

import io.cici.cc.mybatis.lite.reflection.ExceptionUtil;
import io.cici.cc.mybatis.lite.session.Session;
import io.cici.cc.mybatis.lite.util.MapUtil;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * 请不要在mapper接口类里面包含默认方法 桥接方法等。严格按照jdk6 sdk,性能最优。
 */
public class MapperProxy<T> implements InvocationHandler {
    private final Session session;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethodInvoker> methodToMapperMethodInvokerMap;

    public MapperProxy(Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodToMapperMethodInvokerMap, Session session) {
        this.session = session;
        this.mapperInterface = mapperInterface;
        this.methodToMapperMethodInvokerMap = methodToMapperMethodInvokerMap;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }
            return mapperMethodInvoker(method).invoke(proxy, method, args, session);
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
    }

    private MapperMethodInvoker mapperMethodInvoker(Method method) throws Throwable {
        try {
            return MapUtil.computeIfAbsent(methodToMapperMethodInvokerMap, method, m -> {
                return new MapperMethodInvokerImpl(new MapperMethod(session.getConfiguration(), mapperInterface, method));
            });
        } catch (RuntimeException re) {
            Throwable cause = re.getCause();
            throw cause == null ? re : cause;
        }
    }

    interface MapperMethodInvoker {
        Object invoke(Object proxy, Method method, Object[] args, Session session) throws Throwable;
    }

    private static class MapperMethodInvokerImpl implements MapperMethodInvoker {
        private final MapperMethod mapperMethod;

        public MapperMethodInvokerImpl(MapperMethod mapperMethod) {
            this.mapperMethod = mapperMethod;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args, Session session) throws Throwable {
            return mapperMethod.execute(session, args);
        }
    }


}
